package com.cardbaobao.cardbabyclient.api;

import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Base64;

import com.cardbaobao.cardbabyclient.BuildConfig;
import com.cardbaobao.cardbabyclient.R;
import com.cardbaobao.cardbabyclient.utils.ApplicationHelper;
import com.cardbaobao.cardbabyclient.utils.LogUtils;
import com.cardbaobao.cardbabyclient.utils.RsaUtils;

import java.io.File;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

/**
 * Created by Jiangzx on 2017/8/8 9:48.
 */

public class RequestUtils {
    private static final int READ_TIME_OUT = 30000;//30秒
    private static final int WRITE_TIME_OUT = 30000;//30秒
    private static final int CONNECTION_TIME_OUT = 35000;//35秒

    public static final int READ_TIME_OUT_LONG = 90000;//90秒

    private static final TimeOutBuilder DEFAULT_TIME_OUT_BUILDER =
            new TimeOutBuilder(READ_TIME_OUT, WRITE_TIME_OUT, CONNECTION_TIME_OUT);

    public static class TimeOutBuilder {
        private int readTimeOutMilliSecond;
        private int writeTimeOutMilliSecond;
        private int connectTimeOutMilliSecond;

        public TimeOutBuilder(int readTimeOutMilliSecond,
                              int writeTimeOutMilliSecond,
                              int connectTimeOutMilliSecond) {
            this.readTimeOutMilliSecond = readTimeOutMilliSecond;
            this.writeTimeOutMilliSecond = writeTimeOutMilliSecond;
            this.connectTimeOutMilliSecond = connectTimeOutMilliSecond;
        }
    }

    private static Key publicKey;
    private static Key privateKey;

    private static OkHttpClient okHttpClient;

    private static void initOkHttpClient(int readTimeOutMilliSecond,
                                         int writeTimeOutMilliSecond,
                                         int connectTimeOutMilliSecond) {
        okHttpClient = new OkHttpClient.Builder().
                readTimeout(readTimeOutMilliSecond, TimeUnit.MILLISECONDS).
                writeTimeout(writeTimeOutMilliSecond, TimeUnit.MILLISECONDS).
                connectTimeout(connectTimeOutMilliSecond, TimeUnit.MILLISECONDS).
                build();
    }

    /**
     * 请求参数加密，请求结果解密
     *
     * @param url    请求url
     * @param params key-value
     */
    public static Observable<String> postEncryptDecrypt(final String url, final Map<String, Object> params) {
        return postEncryptDecrypt(url, params, DEFAULT_TIME_OUT_BUILDER);
    }

    /**
     * 自定义超时时间的post请求，请求参数加密，请求结果解密
     *
     * @param url            url
     * @param params         请求参数
     * @param timeOutBuilder 自定义超时时间集，时间为毫秒
     */
    public static Observable<String> postEncryptDecrypt(final String url,
                                                        final Map<String, Object> params,
                                                        final TimeOutBuilder timeOutBuilder) {
        Observable.OnSubscribe<String> onSubscribe = new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                Request.Builder requestBuilder = new Request.Builder().url(url);
                String jsonStr = "";
                String encryptRequestParamsStr = "";
                if (params != null && params.size() > 0) {
                    jsonStr = mapToJsonStr(params);
                    encryptRequestParamsStr = encryptRequestParams(jsonStr);
                }
                RequestBody body = new FormBody.Builder().
                        add("params", encryptRequestParamsStr).build();
                requestBuilder.post(body);
                Request request = requestBuilder.build();
                if (okHttpClient == null || okHttpClient.readTimeoutMillis() != timeOutBuilder.readTimeOutMilliSecond) {
                    initOkHttpClient(timeOutBuilder.readTimeOutMilliSecond,
                            timeOutBuilder.writeTimeOutMilliSecond,
                            timeOutBuilder.connectTimeOutMilliSecond);
                }
                try {
                    Response response = okHttpClient.newCall(request).execute();
                    if (response.isSuccessful()) {
                        //请求成功
                        String result = decryptStr(response.body().string());
                        LogUtils.d("success \nurl:" + url +
                                "\nparams:" + jsonStr +
                                "\nencrypted:" + encryptRequestParamsStr +
                                "\nresult:" + result);
                        subscriber.onNext(result);
                        subscriber.onCompleted();
                    } else {
                        //请求失败
                        int code = response.code();
                        LogUtils.w("failure \nurl:" + url +
                                "\nparams:" + jsonStr +
                                "\nencrypted:" + encryptRequestParamsStr +
                                "\nresponseCode:" + code);
                        RuntimeException e = new RuntimeException("请求异常，code:" + code);
                        subscriber.onError(e);
                    }
                } catch (Exception e) {
                    LogUtils.e("error\nurl:" + url +
                            "\nparams:" + jsonStr +
                            "\nencrypted:" + encryptRequestParamsStr +
                            "\n" + e.getMessage());
                    subscriber.onError(e);
                }
            }
        };
        return Observable.unsafeCreate(onSubscribe).
                subscribeOn(Schedulers.io()).
                observeOn(AndroidSchedulers.mainThread());
    }

    public static Observable<String> postFile(final String url,
                                              final String fileAbsolutePath,
                                              final Map<String, Object> params) {
        Observable.OnSubscribe<String> onSubscribe = new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                Request.Builder requestBuilder = new Request.Builder().url(url);
                String jsonStr = "";
                String encryptRequestParamsStr = "";
                if (params != null) {
                    jsonStr = mapToJsonStr(params);
                    encryptRequestParamsStr = encryptRequestParams(jsonStr);
                }
                File file = new File(fileAbsolutePath);
                RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"),
                        file);
                RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM).
                        addFormDataPart("params", encryptRequestParamsStr).
                        addFormDataPart("upload", fileAbsolutePath, fileBody).build();
                Request request = requestBuilder.post(requestBody).build();
                if (okHttpClient == null || okHttpClient.readTimeoutMillis() != READ_TIME_OUT) {
                    initOkHttpClient(READ_TIME_OUT, WRITE_TIME_OUT, CONNECTION_TIME_OUT);
                }
                try {
                    Response response = okHttpClient.newCall(request).execute();
                    if (response.isSuccessful()) {
                        //请求成功
                        String result = decryptStr(response.body().string());
                        LogUtils.d("success \nurl:" + url +
                                "\nfilePath:" + fileAbsolutePath +
                                "\nparams:" + jsonStr +
                                "\nencrypted:" + encryptRequestParamsStr +
                                "\nresult:" + result);
                        subscriber.onNext(result);
                        subscriber.onCompleted();
                    } else {
                        //请求失败
                        int code = response.code();
                        LogUtils.w("failure \nurl:" + url +
                                "\nfilePath:" + fileAbsolutePath +
                                "\nparams:" + jsonStr +
                                "\nencrypted:" + encryptRequestParamsStr +
                                "\nresponseCode:" + code);
                        RuntimeException e = new RuntimeException("请求异常，code:" + code);
                        subscriber.onError(e);
                    }
                } catch (Exception e) {
                    LogUtils.e("error\nurl:" + url +
                            "\nfilePath:" + fileAbsolutePath +
                            "\nparams:" + jsonStr +
                            "\nencrypted:" + encryptRequestParamsStr +
                            "\n" + e.getMessage());
                    subscriber.onError(e);
                }
            }
        };
        return Observable.unsafeCreate(onSubscribe).
                subscribeOn(Schedulers.io()).
                observeOn(AndroidSchedulers.mainThread());
    }

    private static String mapToJsonStr(@NonNull Map<String, Object> params) {
        StringBuilder builder = new StringBuilder("{");
        Object o;
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            builder.append("\"");
            builder.append(entry.getKey());
            builder.append("\":");
            o = entry.getValue();
            if (o instanceof Integer) {
                builder.append(((Integer) o).intValue());
            } else if (o instanceof Long) {
                builder.append(((Long) o).longValue());
            } else if (o instanceof Float) {
                builder.append(((Float) o).floatValue());
            } else if (o instanceof Double) {
                builder.append(((Double) o).doubleValue());
            } else if (o instanceof Boolean) {
                builder.append(((Boolean) o).booleanValue());
            } else {
                builder.append("\"");
                builder.append(o == null ? "" : o.toString());
                builder.append("\"");
            }
            builder.append(",");
        }
        //去除最后一个","
        builder.deleteCharAt(builder.length() - 1);
        builder.append("}");
        return builder.toString();
    }

    /**
     * 将params请求参数转成json并加密
     *
     * @param inputStr 需要加密的字符串
     * @return 加密后的请求参数，发生异常时，返回空字符串
     */
    private static String encryptRequestParams(String inputStr) {
        //获取公钥
        if (publicKey == null) {
            //获取公钥字符串[base64编码]
            Context context = ApplicationHelper.getApplicationContext();
            String publicKeyStr = context.getString(R.string.rsa_key_public).
                    concat(BuildConfig.key_public);
            //解码公钥字符串
            byte[] publicKeyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT);
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
            try {
                publicKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);
            } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        }
        return RsaUtils.encryptByPublicKey(inputStr, publicKey);
    }

    /**
     * 对字符串解密
     *
     * @param inputStr 加密后的字符串
     * @return 解密后的字符串，发生异常则返回空字符串
     */
    private static String decryptStr(String inputStr) {
        //获取私钥
        if (privateKey == null) {
            //获取私钥字符串[base64编码]
            Context context = ApplicationHelper.getApplicationContext();
            String privateKeyStr = context.getString(R.string.rsa_key_private).
                    concat(BuildConfig.key_private);
            byte[] privateKeyBytes = Base64.decode(privateKeyStr, Base64.DEFAULT);
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
            try {
                privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec);
            } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        }
        return RsaUtils.decryptByPrivateKey(inputStr, privateKey);
    }
}
